6  Anhang: Data Wrangling

Data Wrangling bezeichnet im Kontext der psychologischen Datenanalyse mit R den systematischen Prozess der Aufbereitung, Bereinigung und Umstrukturierung von Rohdaten, damit sie für statistische Auswertungen und Interpretationen nutzbar werden. Ziel ist es, aus unübersichtlichen Rohdaten ein strukturiertes und hochwertiges Daten-Set zu erzeugen, das sich zuverlässig und nachvollziehbar für weiterführende statistische Analysen, Modellierungen und Visualisierungen in R nutzen lässt.

Im Folgenden werden dazu verschiedene Funktionen und Techniken dargestellt.

6.1 Notebooks

In diesem Kurs arbeiten wir mit R- bzw. Quarto-Notebooks. Diese enthalten sowohl normalen Text als auch “Chunks” von R-Code. Chunks sind Abschnitte von R-Code. Diese Chunks lassen sich mit einem Klick auf Code –> Insert Chunk (oder der Tastenkombination Alt + Ctrl + I / unter Mac: Alt + Cmd + I) einfügen. Diese Chunks lassen sich mit einem Klick auf den grünen Play-Button ganz rechts ausführen. Der Pfeil nach unten Button (zweiter von Rechts) führt alle Chunks, die vor dem gewählten Chunk gelagert sind, aus.

6.2 Pakete laden

Zunächst laden wir die Pakete, die wir für die Sitzung brauchen.

if (!require("pacman")) install.packages("pacman")
Lade nötiges Paket: pacman
pacman::p_load(psych, tidyverse)
data(sat.act)
df_beispiel <-as_tibble(sat.act)

6.3 Hilfe-Menü und Dokumentation

Für jede Funktion in einem geladenen Paket können Sie einfach ? vor den Funktionsnamen schreiben, um das Hilfemenü bzw. ihre Dokumentation aufzurufen. Dies hilft Ihnen, den Zweck der Funktion, ihre Argumente und Ausgaben zu verstehen. Sie können auch auf den Namen der Funktion klicken (an beliebiger Stelle im Namen) und “F1” drücken.

?head
head(df_beispiel)

6.4 Struktur von Funktionen

Fast alles in R geschieht mittels Funktionen. Funktionen nehmen Inputs und transformieren sie nach den Regeln der Funktion in Outputs. Die Funktion kann mittels Argumenten gesteuert werden.

  • Funktionen sind i.d.R. benannt (hier head, mean) und mit Klammern () gekennzeichet. In der Klammer befinden sich die Argumente der Funktion, mit einem Komma getrennt. Argumente können benannt oder unbenannt sein. Werden Sie nicht benannt, folgt R der Reihenfolge der erwarteten Argumente, wie sie in der Funktionsdokumentation beschrieben sind.
  • Unbenannt hier: 10 bei head()
  • Benannt hier: na.rm = TRUE bei mean(), wobei na.rm der Name und TRUE der Inhalt des Argument sind.
head(df_beispiel, 10)
# A tibble: 10 × 6
   gender education   age   ACT  SATV  SATQ
    <int>     <int> <int> <int> <int> <int>
 1      2         3    19    24   500   500
 2      2         3    23    35   600   500
 3      2         3    20    21   480   470
 4      1         4    27    26   550   520
 5      1         2    33    31   600   550
 6      1         5    26    28   640   640
 7      2         5    30    36   610   500
 8      1         3    19    22   520   560
 9      2         4    23    22   400   600
10      2         5    40    35   730   800
mean(df_beispiel$age, na.rm = TRUE)
[1] 25.59429

6.5 Zuweisung / Assignment mittels <-

  • <- führt die Zuweisung (assignment) von Inhalten der rechten Seite unter dem Namen auf der linken Seite aus. Wir nennen die zugewiesenen Inhalte, etwas verkürzt gesagt, Objekte oder Variablen.
durchschnitt_von_SATV <- mean(df_beispiel$SATV, na.rm = TRUE)

In diesem Beispiel ist durchschnitt_von_SATV der Name des Objekts/Variable.

6.6 Typen von Objekten

Die häufigsten Typen von Objekten/Variablen sind:

  • Vektor, numerisch
  • Vektor, Character (Buchstaben)
  • data frame (Datenframe, Tabelle)
  • tibble - eine verbesserte Variante eines data frame
  • list (Liste)

Einen Vektor erstellt man mit der c() Funktionen

vektor_numerisch <- c(1,2,3,4,5)
vektor_character <- c("Zytglogge", "Hirschengraben", "Bahnhof", "Bundesplatz", "Viktoriaplatz")
datenframe <- data.frame(Nummer = vektor_numerisch, Ort = vektor_character)
datenframe
  Nummer            Ort
1      1      Zytglogge
2      2 Hirschengraben
3      3        Bahnhof
4      4    Bundesplatz
5      5  Viktoriaplatz
tibble_beispiel <- tibble(Nummer = vektor_numerisch, Ort = vektor_character)
tibble_beispiel
# A tibble: 5 × 2
  Nummer Ort           
   <dbl> <chr>         
1      1 Zytglogge     
2      2 Hirschengraben
3      3 Bahnhof       
4      4 Bundesplatz   
5      5 Viktoriaplatz 

Wie wir sehen, sind Datenframes i.d.R. aus Vektoren der gleichen Länge, aber oft unterschiedlichen Typs, aufgebaut.

beispielliste <- list(x1 = vektor_numerisch, x2 = vektor_character, c(6,7,8), x3 = tibble_beispiel, "test")
beispielliste
$x1
[1] 1 2 3 4 5

$x2
[1] "Zytglogge"      "Hirschengraben" "Bahnhof"        "Bundesplatz"   
[5] "Viktoriaplatz" 

[[3]]
[1] 6 7 8

$x3
# A tibble: 5 × 2
  Nummer Ort           
   <dbl> <chr>         
1      1 Zytglogge     
2      2 Hirschengraben
3      3 Bahnhof       
4      4 Bundesplatz   
5      5 Viktoriaplatz 

[[5]]
[1] "test"

Listen sind extrem flexibel, was ihre Anhalte angeht - wir können quasi alles reinpacken. Das macht sie teils aber auch schwer zu durchschauen. Wenn möglich empfiehlt es sich, mit tibble/data.frame zu arbeiten; dies spart kognitive Ressourcen.

6.7 Die “Pipe” (%>% oder |>)

%>% ist der ursprüngliche Pipe-Operator, der für das Paket {magrittr} entwickelt wurde und in den gesamten tidyverse-Paketen verwendet wird. Er ist etwas langsamer, aber auch flexibler. |> ist eine neuere Version des Pipe-Operators, die in Base-R integriert wurde. Sie ist etwas schneller, aber weniger flexibel.

Die Ausgabe des linken Teils der Pipe wird als Eingabe für den rechten Teil der Pipe verwendet, in der Regel als erstes Argument oder als Datenargument. Effektiv transportiert die Pipe den Inhalt von links nach rechts weiter. Schreiben lässt sich die Pipe mit den Tasten Ctrl + Shift + M (unter Windows; unter Mac Cmd + Shift + M?).

# use a function without the pipe
example_without_pipe <- select(df_beispiel, gender, ACT)

# use a function with the pipe. 
example_with_pipe <- df_beispiel |> 
  select(gender, ACT)


example_without_pipe
# A tibble: 700 × 2
   gender   ACT
    <int> <int>
 1      2    24
 2      2    35
 3      2    21
 4      1    26
 5      1    31
 6      1    28
 7      2    36
 8      1    22
 9      2    22
10      2    35
# ℹ 690 more rows
example_with_pipe
# A tibble: 700 × 2
   gender   ACT
    <int> <int>
 1      2    24
 2      2    35
 3      2    21
 4      1    26
 5      1    31
 6      1    28
 7      2    36
 8      1    22
 9      2    22
10      2    35
# ℹ 690 more rows
# check they produce identical results
identical(example_without_pipe, example_with_pipe)
[1] TRUE

6.7.1 Warum lohnt es sich, die Pipe zu nutzen?

Der Pipe-Operator ermöglicht es uns, Code zu schreiben, der von oben nach unten gelesen wird und einer Abfolge von Schritten folgt – so, wie Menschen Schritte organisieren und beschreiben. Ohne den Pipe-Operator wird der Code von innen nach aussen geschrieben, auf eine Weise, die der Computer versteht, aber für Menschen weniger intuitiv ist. Die Unterscheide in der Lesbarkeit zeigen sich vor allem bei komplexeren, verketteten Funktionen.

Wir verwenden im Folgenden einen Datenframe, wählen mit select() Spalten aus, und erstellen ihre Mittelwerte mit colMeans(). Ohne Pipe lesen wir von innen nach aussen: select(…), dann colMeans(…); mit Pipe lesen und schreiben wir: Objekt - select() - colMeans(), was einfacher nachzuvollziehen ist. Jede Funktion macht etwas, die Pipe gibt jeweils den transformierten Inhalt weiter an die nächste Funktion.

# use a function without the pipe
example_without_pipe <- colMeans(select(df_beispiel, SATV, SATQ), na.rm = TRUE)

example_without_pipe
    SATV     SATQ 
612.2343 610.2169 
# use a function with the pipe. 
example_with_pipe <- df_beispiel |> 
  select(SATV, SATQ) |> 
  colMeans(na.rm = TRUE)

# check they produce identical results
identical(example_without_pipe, example_with_pipe)
[1] TRUE

6.8 Spaltennamen anzeigen

Die meiste Zeit verbringen wir mit Data Frames - effektiv Tabellen. Wie können wir herausfinden, welche Variablen sich in einem Data Frame befinden? Wir können den Data Frame anzeigen, aber es kann auch hilfreich sein, ihn auszugeben. Zu wissen, welche Variablen vorhanden sind, ist einer der ersten Schritte, um mit den Daten zu arbeiten.

# print all column names
colnames(df_beispiel)
[1] "gender"    "education" "age"       "ACT"       "SATV"      "SATQ"     
# print all column names as a vector using the pipe
df_beispiel |> 
  colnames()
[1] "gender"    "education" "age"       "ACT"       "SATV"      "SATQ"     

6.9 Spalten umbenennen - rename()

Oft sind die Variablennamen nicht intuitiv. Ein früher Schritt bei jeder Datenaufbereitung ist es, sie mithilfe der Funktion rename() intuitiver zu gestalten. Als Argumente schreiben wir jeweils neuer_name = alter_name.

df_beispiel_renamed <- df_beispiel |> 
  rename(Alter = age,
         Bildung = education) 

6.10 Auswahl und Extraktion

Es gibt verschieden Möglichkeiten, Inhalte aus Variablen auszuwählen und zu extrahieren:

  • select() / |> slice() / pluck()
  • []
  • $

Im ersten Beispiel wollen wir die Variable x aus unserem Dataframe df_beispiel auswählen.

df_beispiel |> select(ACT) # select mit pipe
# A tibble: 700 × 1
     ACT
   <int>
 1    24
 2    35
 3    21
 4    26
 5    31
 6    28
 7    36
 8    22
 9    22
10    35
# ℹ 690 more rows
df_beispiel[,"ACT"] # []
# A tibble: 700 × 1
     ACT
   <int>
 1    24
 2    35
 3    21
 4    26
 5    31
 6    28
 7    36
 8    22
 9    22
10    35
# ℹ 690 more rows
df_beispiel$ACT # $ 
  [1] 24 35 21 26 31 28 36 22 22 35 32 29 21 35 27 27 33 32 28 32 28 30 31 30 31
 [26] 30 30 21 28 33 28 24 27 31 33 29 28 24 27 31 28 29 30 36 36 26 33 27 23 33
 [51] 35 28 28 33 34 26 36 28 18 34 34 32 36 34 21 31 22 29 29 24 26 29 28 32 32
 [76] 27 28 32 15 21 26 25 31 26 28 33 24 32 31 30 32 36 32 30 27 35 29 33 30 32
[101] 26 36 36 27 26 29 23 23 29 32 32 30 28 23 36 26 36 34 23 28 23 28 32 16 29
[126] 29 29 32 26 23 35 28 25 23 36 30 30 33 31 28 35 32 26 34 34 32 24 32 23 35
[151] 24 30 26 26 30 34 19 23 31 28 32 25 26 31 34 31 30 36 32 33 29 25 30 34 32
[176] 29 25 32 33 35 26 24 23 35 27 28 27 28 32 34 27 31 17 25 36 29 30 29 28 28
[201] 30 30 31 29 28 26 31 25 21 24 36 34 32 23 36 34 28 19 31 21 19 22 30 18 36
[226] 28 24 28 23 30 22 23 25 25 24 30 30 24 22 22 27 27 35 16 28 25 23 32 25 28
[251] 35 28 24 27 30 34 35 33 19 25 35 26 24 20 30 32 30 29 28 23 36 25 29 20 32
[276] 24 30 33 21 20 32 30 31 25 18 25 26 29 33 31 25 27 36 32 32 26 23 34 27 30
[301] 30 28 25 20 26 33 23 33 25 27 26 28 28 35 21 28 29 32 30 18 32 32 27 17 28
[326] 21 30 25 32 35 32 16 27 27 20 31 30 24 26 19 24 27 35 27 32 26 25 29 36 30
[351] 25 22 27 23 34 31 33 31 26 35 33 29 28 27 27 31 31 32 22 35 25 25 23 26 27
[376] 27 34 30 32 26 25 29 22 28 35 24 27 32 17 18 20 35 32 24 32 29 28 27 27 20
[401] 22 33 30 34 28 32 30 23 34 26 32 22 29 23 35 32 31 27 29 29 33 35 21 24 29
[426] 30 30 32 28 28 25 28 33 32 20 21 28 35 26  3 21 21 32 31 27 35 32 24 34 32
[451] 31 36 23 28 35 22 26 29 29 21 31 36 25 27 17 32 32 22 26 30 25 25 34 24 20
[476] 32 25 21 22 35 20 23 23 29 21 28 35 34 36 15 34 28 24 23 22 32 21 35 26 15
[501] 32 28 28 25 30 28 20 22 20 18 32 35 30 25 26 21 24 16 30 28 34 35 25 34 27
[526] 31 27 34 31 27 27 36 30 15 23 26 35 33 34 35 27 31 33 29 21 19 25 30 21 23
[551] 32 21 25 27 29 20 32 28 32 32 35 18 32 31 20 32 33 34 31 30 34 32 28 34 31
[576] 29 34 32 31 32 32 27 33 30 27 31 33 31 33 30 32 31 33 35 32 32 33 36 26 34
[601] 36 31 30 24 34 34 29 30 31 31 26 32 30 28 32 32 25 32 36 25 29 28 32 28 33
[626] 35 27 32 35 29 26 31 34 26 35 35 28 27 20 31 34 22 35 22 22 35 34 32 31 25
[651] 32 31 29 32 27 27 34 22 36 36 34 33 34 21 28 29 25 31 29 31 22 23 33 36 30
[676] 30 36 32 33 28 31 32 34 33 36 30 30 27 33 32 33 25 26 27 26 30 27 31 32 25

Wie wir sehen, behalten |> und [] die Struktur des Inputs (ein Datenframe) standardmässig bei (ihr Output ist identisch), während $ die Struktur “fallen lässt” und eine simplere Struktur wählt (Vektor statt Datenframe). Entsprechend ist der Output auch anders.

Im zweiten Beispiel wollen wir die Variablen x und y und die ersten zwei Zeilen aus unserem Dataframe df_beispiel auswählen. Nach dem “tidy” Verfahren wählen wir dafür Datenframe |> select() |> slice(). Nach dem “klassischen” Verfahren wählen wir in [] zuerst die Zeilen, dann die Spalten aus. Die Spalten müssen wir hier als richtigen Vektor eingeben, d.h. mittels c() “x”und “y” (in Anführungszeichen) angeben.

df_beispiel |> select(SATV, SATQ) |> slice(1:2)
# A tibble: 2 × 2
   SATV  SATQ
  <int> <int>
1   500   500
2   600   500
df_beispiel[1:2,c("SATV", "SATQ")]
# A tibble: 2 × 2
   SATV  SATQ
  <int> <int>
1   500   500
2   600   500
c(df_beispiel$SATV[1:2], df_beispiel$SATQ[1:2])
[1] 500 600 500 500

Listen sind etwas komplizierter. Hier wird analog zu select() pluck() verwendet.

beispielliste <- list(x1 = vektor_numerisch, x2 = vektor_character, c(6,7,8), x3 = tibble_beispiel, "test")
# tidy
beispielliste |> pluck(2)
[1] "Zytglogge"      "Hirschengraben" "Bahnhof"        "Bundesplatz"   
[5] "Viktoriaplatz" 
beispielliste |> pluck("x2")
[1] "Zytglogge"      "Hirschengraben" "Bahnhof"        "Bundesplatz"   
[5] "Viktoriaplatz" 
# [] und [[]]
beispielliste[2]
$x2
[1] "Zytglogge"      "Hirschengraben" "Bahnhof"        "Bundesplatz"   
[5] "Viktoriaplatz" 
beispielliste[[2]]
[1] "Zytglogge"      "Hirschengraben" "Bahnhof"        "Bundesplatz"   
[5] "Viktoriaplatz" 
# $ - geht nur mit benannten Listenelementen
beispielliste$x2
[1] "Zytglogge"      "Hirschengraben" "Bahnhof"        "Bundesplatz"   
[5] "Viktoriaplatz" 

6.10.1 Auswahl von Spalten / Variablen mittels Helfern

Beim Data Wrangling bearbeiten wir häufig Gruppen von Variablen / Spalten. Damit wir nicht immer jede einzelne Variable ausschreiben müssen, gibt es verschiedene Methoden. Hilfreich sind vor allem die sogenannten “tidyselect” Helferfunktionen. Diese können wir in select(), aber auch vielen anderen Funktionen verwenden, bei der wir Spalten aus einem Datensatz ansteuern.

Die häufigsten Helfer sind :, c(), starts_with(), ends_with(), contains(), all_of(), num_range().

names(df_beispiel)
[1] "gender"    "education" "age"       "ACT"       "SATV"      "SATQ"     
df_beispiel |> select(age:gender)
# A tibble: 700 × 3
     age education gender
   <int>     <int>  <int>
 1    19         3      2
 2    23         3      2
 3    20         3      2
 4    27         4      1
 5    33         2      1
 6    26         5      1
 7    30         5      2
 8    19         3      1
 9    23         4      2
10    40         5      2
# ℹ 690 more rows
df_beispiel |> select(c(age,gender))
# A tibble: 700 × 2
     age gender
   <int>  <int>
 1    19      2
 2    23      2
 3    20      2
 4    27      1
 5    33      1
 6    26      1
 7    30      2
 8    19      1
 9    23      2
10    40      2
# ℹ 690 more rows
df_beispiel |> select(starts_with("S"))
# A tibble: 700 × 2
    SATV  SATQ
   <int> <int>
 1   500   500
 2   600   500
 3   480   470
 4   550   520
 5   600   550
 6   640   640
 7   610   500
 8   520   560
 9   400   600
10   730   800
# ℹ 690 more rows
df_beispiel |> select(ends_with("V"))
# A tibble: 700 × 1
    SATV
   <int>
 1   500
 2   600
 3   480
 4   550
 5   600
 6   640
 7   610
 8   520
 9   400
10   730
# ℹ 690 more rows
df_beispiel |> select(contains("SAT"))
# A tibble: 700 × 2
    SATV  SATQ
   <int> <int>
 1   500   500
 2   600   500
 3   480   470
 4   550   520
 5   600   550
 6   640   640
 7   610   500
 8   520   560
 9   400   600
10   730   800
# ℹ 690 more rows
meine_variablen <- c("SATV", "SATQ", "gender")
df_beispiel |> select(all_of(meine_variablen))
# A tibble: 700 × 3
    SATV  SATQ gender
   <int> <int>  <int>
 1   500   500      2
 2   600   500      2
 3   480   470      2
 4   550   520      1
 5   600   550      1
 6   640   640      1
 7   610   500      2
 8   520   560      1
 9   400   600      2
10   730   800      2
# ℹ 690 more rows

Wir können auch mit logischen Operatoren arbeiten und diese verketten. Zu logischen Operatoren siehe auch: Einführung in R, Kapitel 2.1.2.

data(bfi)  
names(bfi) #Big Five Items, OCEAN mit je 5 Items sowie gender, education und age
 [1] "A1"        "A2"        "A3"        "A4"        "A5"        "C1"       
 [7] "C2"        "C3"        "C4"        "C5"        "E1"        "E2"       
[13] "E3"        "E4"        "E5"        "N1"        "N2"        "N3"       
[19] "N4"        "N5"        "O1"        "O2"        "O3"        "O4"       
[25] "O5"        "gender"    "education" "age"      
# bfi von normalen data.frame zu tibble upgraden
bfi <- as_tibble(bfi)

bfi |> select(starts_with("E")) |> head() # auch education dabei
# A tibble: 6 × 6
     E1    E2    E3    E4    E5 education
  <int> <int> <int> <int> <int>     <int>
1     3     3     3     4     4        NA
2     1     1     6     4     3        NA
3     2     4     4     4     5        NA
4     5     3     4     4     4        NA
5     2     2     5     4     5        NA
6     2     1     6     5     6         3
bfi |> select(starts_with("E") & !all_of("education")) |> head() # nur die Extraversion Items, kein education
# A tibble: 6 × 5
     E1    E2    E3    E4    E5
  <int> <int> <int> <int> <int>
1     3     3     3     4     4
2     1     1     6     4     3
3     2     4     4     4     5
4     5     3     4     4     4
5     2     2     5     4     5
6     2     1     6     5     6
# oder eleganter mit num_range()
bfi |> select(num_range("E", 1:5)) |> head() # nur die Extraversion Items
# A tibble: 6 × 5
     E1    E2    E3    E4    E5
  <int> <int> <int> <int> <int>
1     3     3     3     4     4
2     1     1     6     4     3
3     2     4     4     4     5
4     5     3     4     4     4
5     2     2     5     4     5
6     2     1     6     5     6
bfi |> select(starts_with("E"), starts_with("C")) |> head() # "," wird als einschliessendes oder (OR) gelesen
# A tibble: 6 × 11
     E1    E2    E3    E4    E5 education    C1    C2    C3    C4    C5
  <int> <int> <int> <int> <int>     <int> <int> <int> <int> <int> <int>
1     3     3     3     4     4        NA     2     3     3     4     4
2     1     1     6     4     3        NA     5     4     4     3     4
3     2     4     4     4     5        NA     4     5     4     2     5
4     5     3     4     4     4        NA     4     4     3     5     5
5     2     2     5     4     5        NA     4     4     5     3     2
6     2     1     6     5     6         3     6     6     6     1     3

6.11 Spalten ändern und neue Anlegen - mutate()

mutate() wird genutzt, um neue Spalten anzulegen oder die Inhalte bestehender Spalten zu ändern. is used to create new columns or to change the contents of existing ones. Oft verändern wir Variablen bei einer Analyse. Typisch ist etwa die Zentrierung von Variablen auf den Mittelwert der Stichprobe (grand mean; damit bekommen die Variablen einen Mittelwert von 0) oder die z-Standardisierung (Variablen bekommen einen Mittelwert von 0 und eine Standardabweichung von 1).

df_beispiel <- df_beispiel |> mutate(ACT_c = scale(ACT, center = TRUE, scale = FALSE),
                                     ACT_z = scale(ACT, center = TRUE, scale = TRUE))
df_beispiel
# A tibble: 700 × 8
   gender education   age   ACT  SATV  SATQ ACT_c[,1] ACT_z[,1]
    <int>     <int> <int> <int> <int> <int>     <dbl>     <dbl>
 1      2         3    19    24   500   500    -4.55     -0.943
 2      2         3    23    35   600   500     6.45      1.34 
 3      2         3    20    21   480   470    -7.55     -1.56 
 4      1         4    27    26   550   520    -2.55     -0.528
 5      1         2    33    31   600   550     2.45      0.509
 6      1         5    26    28   640   640    -0.547    -0.113
 7      2         5    30    36   610   500     7.45      1.55 
 8      1         3    19    22   520   560    -6.55     -1.36 
 9      2         4    23    22   400   600    -6.55     -1.36 
10      2         5    40    35   730   800     6.45      1.34 
# ℹ 690 more rows

6.12 Zeilen filtern

Neben der Mutation/Transformation von Spalten ist das Filtern von Zeilen ein typisches Anliegen bei der Datenaufbereitung. Dies geht mittels filter(). Du kannst den logischen Test für das Filtern auf verschiedene Arten angeben, einschließlich Gleichheit (==), Negation (!=) oder Zugehörigkeit (%in%). Es ist oft besser zu definieren, was du möchtest (mithilfe von Gleichheit oder Zugehörigkeit), anstatt zu definieren, was du nicht möchtest (Negation), da Negationen weniger robust gegenüber neuen Daten mit ungewöhnlichen Werten sind, die du beim Schreiben des Codes nicht bedacht hast. Zum Beispiel könntest du gender != 2 angeben, aber das würde non binary nicht erfassen.

df_beispiel_w <- df_beispiel |> filter(gender == 2)

Du kannst auch mehrere Kriterien in deinem Filteraufruf verwenden, bei denen beide erfüllt sein müssen (x & y) oder bei denen eines von beiden erfüllt sein muss (x | y).

df_beispiel_w_and_edu <- df_beispiel |> filter(gender == 2 & education > 2)
df_beispiel_w_or_edu <- df_beispiel |> filter(gender == 2 | education > 2)


# note that these provide different results - make sure you understand why
identical(df_beispiel_w_and_edu, df_beispiel_w_or_edu)
[1] FALSE

6.13 Übung Data Wrangling

  1. Wähle zunächst die folgenden Variablen aus: education, SATQ.
  2. Erstelle eine neue Variable, in der die SATQ zentriert wird und nenne sie “SATQ_c”. Transformiere education in einen Faktor (as.factor()).
  3. Wähle alle Personen mit High School-Abschluss (Faktor-Level 1) aus.

Führe zunächst jeden Schritt separat und sequentiell aus. Verknüpfe dann alle Funktionen/Schritte mittels mehrerer Pipes. Das führt dazu, dass weniger Objekte in deiner Umgebung existieren und somit weniger Verwirrung oder Fehlerpotenzial entsteht. Genereller Tipp: Wir lösen Programmierprobleme leichter, indem wir sie in kleinere Aufgaben und Probleme zerlegen, diese jeweils einzeln zum Laufen bringen und sie dann wieder zusammenfügen. Wenn man nur das Endprodukt sieht, könnte man leicht denken, der/die Autor:in hätte den Code genau so geschrieben, wie er aussieht. Dabei hat er/sie oft viel ausführlichere Codeabschnitte geschrieben und diese anschließend zusammengeführt.

# auswählen
 df_auswahl <- df_beispiel
# neue variable erstellen
df_neuevariable <- df_auswahl
# filtern
df_filter <- df_neuevariable 

# jetzt alles zusammen
df_kombiniert <- df_beispiel 
# auswählen
df_auswahl <- df_beispiel |> select(education, SATQ)
# neue variable erstellen
df_neuevariable <- df_auswahl |> mutate(
  education = as.factor(education),
  SATQ_c = scale(SATQ, center = TRUE, scale = FALSE))
# filtern
df_filter <- df_neuevariable |> filter(education == 1)

# jetzt alles zusammen
df_kombiniert <- df_beispiel |> 
  select(education, SATQ) |> 
  mutate(
  education = as.factor(education),
  SATQ_c = scale(SATQ, center = TRUE, scale = FALSE)) |> 
  filter(education == 1)

# identical(df_filter, df_kombiniert) # check

6.14 Zusammenfassung über Zeilen

Es ist sehr häufig, dass wir Zusammenfassungen über Zeilen hinweg erstellen müssen. Zum Beispiel, um den Mittelwert und die Standardabweichung einer Spalte wie age zu berechnen. Das kann mit summarize() erledigt werden. Denk daran: mutate() erstellt neue Spalten oder ändert den Inhalt bestehender Spalten, ohne die Anzahl der Zeilen zu verändern. summarize() hingegen reduziert einen Datensatz auf eine einzelne Zeile.

# mean
df_beispiel |> summarize(mean_age = mean(age, na.rm = TRUE))
# A tibble: 1 × 1
  mean_age
     <dbl>
1     25.6
# SD
df_beispiel |> 
  summarize(sd_age = sd(age, na.rm = TRUE))
# A tibble: 1 × 1
  sd_age
   <dbl>
1   9.50
# mean and SD with rounding, illustrating how multiple summarizes can be done in one function call
df_beispiel |> 
  summarize(mean_age = mean(age, na.rm = TRUE),
            mean_age = round(mean_age, digits = 2),
            sd_age = sd(age, na.rm = TRUE),
            sd_age = round(sd_age, digits = 2))
# A tibble: 1 × 2
  mean_age sd_age
     <dbl>  <dbl>
1     25.6    9.5

6.14.1 group_by()

Oft wollen wir einen Datensatz jedoch nicht auf eine einzelne Zeile reduzieren bzw. den gesamten Datensatz zusammenfassen, sondern eine Zusammenfassung für jede (Unter-)Gruppe erstellen. Dies ist hilfreich, insbesondere da wir im Seminar mit Tagen verschachtelt in Personen arbeiten werden.

# illustrate use of group_by() and summarize()
df_beispiel |> 
  group_by(education) |> 
  summarize(ACT_c = mean(ACT_z, na.rm = TRUE))
# A tibble: 6 × 2
  education   ACT_c
      <int>   <dbl>
1         0 -0.223 
2         1 -0.219 
3         2 -0.325 
4         3 -0.0524
5         4  0.148 
6         5  0.219 

6.14.2 Komplexere Zusammenfassungen

Ähnlich wie bei mutate() kann auch die Operation für summarize() komplexer sein, z. B. das Finden des Mittelwerts einer logischen Operation (d.h., logische Operatoren nutzend, wie &, <, >, | etc.) , um einen Anteil zu berechnen. Im Folgenden berechnen wir den Anteil von niedrigen SAT Verbal Scores (hier definiert als -1 Standardabweichungen oder niedriger) gruppiert nach Bildung.

df_beispiel |> 
  mutate(SATV_z = scale(SATV)) |> 
  group_by(education) |>
  summarize(SATV_unterdurchschnitt = mean(SATV_z < -1, na.rm = TRUE))
# A tibble: 6 × 2
  education SATV_unterdurchschnitt
      <int>                  <dbl>
1         0                 0.193 
2         1                 0.2   
3         2                 0.25  
4         3                 0.135 
5         4                 0.0942
6         5                 0.0851

Mit across() kannst du Zusammenfassungen (oder auch Änderungen mit mutate()) über mehrere Spalten hinweg auf dieselbe Weise durchführen. Wir werden hier nicht alle Möglichkeiten oder Details zu across() behandeln, aber es ist wichtig zu wissen, dass es möglich ist. Zum Beispiel:

df_beispiel |> 
  # ... calculate the mean of every numeric column in the dataset ...
  summarise(across(where(is.numeric), mean, na.rm = TRUE)) |> 
  # ... and then round every column to one decimal place
  mutate(across(everything(), round, digits = 2))
Warning: There was 1 warning in `summarise()`.
ℹ In argument: `across(where(is.numeric), mean, na.rm = TRUE)`.
Caused by warning:
! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
Supply arguments directly to `.fns` through an anonymous function instead.

  # Previously
  across(a:b, mean, na.rm = TRUE)

  # Now
  across(a:b, \(x) mean(x, na.rm = TRUE))
# A tibble: 1 × 8
  gender education   age   ACT  SATV  SATQ ACT_c ACT_z
   <dbl>     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1   1.65      3.16  25.6  28.6  612.  610.     0     0

6.15 Übung summarize()

Berechne min, max, mean, und SD aller Antworten auf dem selbstberichteten SAT Verbal Score (SATV).

# data_selfreport_tidy

df_beispiel |> 
  summarize()
# A tibble: 1 × 0

Für die zweiten Übung arbeiten wir mit dem Datensatz BFI, der Big Five Items beinhaltet (s. Dokumentation des Datensatzes). Berechne, getrennt nach Gender, Mittelwert un SD von C1 (ein Gewissenhaftigkeit-Item).

data(bfi)
head(bfi)
      A1 A2 A3 A4 A5 C1 C2 C3 C4 C5 E1 E2 E3 E4 E5 N1 N2 N3 N4 N5 O1 O2 O3 O4
61617  2  4  3  4  4  2  3  3  4  4  3  3  3  4  4  3  4  2  2  3  3  6  3  4
61618  2  4  5  2  5  5  4  4  3  4  1  1  6  4  3  3  3  3  5  5  4  2  4  3
61620  5  4  5  4  4  4  5  4  2  5  2  4  4  4  5  4  5  4  2  3  4  2  5  5
61621  4  4  6  5  5  4  4  3  5  5  5  3  4  4  4  2  5  2  4  1  3  3  4  3
61622  2  3  3  4  5  4  4  5  3  2  2  2  5  4  5  2  3  4  4  3  3  3  4  3
61623  6  6  5  6  5  6  6  6  1  3  2  1  6  5  6  3  5  2  2  3  4  3  5  6
      O5 gender education age
61617  3      1        NA  16
61618  3      2        NA  18
61620  2      2        NA  17
61621  5      2        NA  17
61622  3      1        NA  17
61623  1      2         3  21
df_beispiel |> 
  summarize(min_satv = min(SATV, na.rm = TRUE),
            max_satv = max(SATV, na.rm = TRUE),
            mean_satv = mean(SATV, na.rm = TRUE),
            sd_satv = sd(SATV, na.rm = TRUE))
# A tibble: 1 × 4
  min_satv max_satv mean_satv sd_satv
     <int>    <int>     <dbl>   <dbl>
1      200      800      612.    113.
bfi |> group_by(gender) |> 
  summarize(C1_na = sd(C1, na.rm = TRUE),
            C1_mean = mean(C1, na.rm = TRUE))
# A tibble: 2 × 3
  gender C1_na C1_mean
   <int> <dbl>   <dbl>
1      1  1.24    4.48
2      2  1.24    4.52

6.16 Kontrolliere deinen Lernfortschritt

Was ist der Unterschied zwischen mutate() und summarize()? Bekomme ich dasselbe Ergebnis, wenn ich die falsche Funktion verwende? Zum Beispiel: mutate(mean_age = mean(age, na.rm = TRUE)) vs.summarize(mean_age = mean(age, na.rm = TRUE)).

6.17 Daten auf Festplatte schreiben

Am Ende von Data Wrangling Prozessen ist es sinnvoll, die Ergebnisse festzuhalten, damit wir sie nicht verlieren, bzw. beim nächsten Mal nicht alles von vorne ausführen müssen.

df_beispiel_standardisiert <- df_beispiel |> mutate(across(c("SATV", "SATQ", "ACT"),
                                                           scale))

save(df_beispiel_standardisiert, file =  "../data/df_beispiel_standardisiert.RData")